package routes

import (
	"bufio"
	"bytes"
	"context"
	"deployment-service/domain"
	k8si "deployment-service/k8s"
	k8ssvc "deployment-service/k8s/impl"
	"deployment-service/utils"
	"fmt"
	underscore "github.com/ahl5esoft/golang-underscore"
	"github.com/gin-gonic/gin"
	"io"
	typeV1 "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
	utilyaml "k8s.io/apimachinery/pkg/util/yaml"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/restmapper"
	"net/http"
	"strings"
)

func deployImpl(k8sClient k8si.IK8s, deploymentYmlText string, ctx context.Context) error {

	clientSet, err := k8sClient.GetClientSet()
	if err != nil {
		return err
	}

	dynamicClient, err := k8sClient.GetDynamicClient()
	if err != nil {
		return err
	}

	if len(deploymentYmlText) == 0 {
		return err
	}

	//
	configs := strings.Split(
		deploymentYmlText,
		"---",
	)
	for _, config := range configs {
		reader := bytes.NewReader(
			[]byte(config),
		)
		decoder := utilyaml.NewYAMLOrJSONDecoder(reader, 100)
		for {
			var raw runtime.RawExtension
			if err = decoder.Decode(&raw); err != nil {
				break
			}

			obj, gvk, err := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme).Decode(raw.Raw, nil, nil)
			if err != nil {
				return err
			}

			unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
			if err != nil {
				return err
			}

			gr, err := restmapper.GetAPIGroupResources(
				clientSet.Discovery(),
			)
			if err != nil {
				return err
			}

			mapping, err := restmapper.NewDiscoveryRESTMapper(gr).RESTMapping(
				gvk.GroupKind(),
				gvk.Version,
			)
			if err != nil {
				return err
			}

			unstructuredObj := &unstructured.Unstructured{
				Object: unstructuredMap,
			}
			var dri dynamic.ResourceInterface
			if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
				dri = dynamicClient.Resource(mapping.Resource).Namespace(
					unstructuredObj.GetNamespace(),
				)
			} else {
				dri = dynamicClient.Resource(mapping.Resource)
			}

			_, err = dri.Get(
				ctx,
				unstructuredObj.GetName(),
				metav1.GetOptions{},
			)
			if err != nil {
				if _, err = dri.Create(ctx, unstructuredObj, metav1.CreateOptions{}); err != nil {
					return err
				}
			} else {
				if unstructuredObj.GetKind() == "Service" {
					continue
				}

				if _, err = dri.Update(ctx, unstructuredObj, metav1.UpdateOptions{}); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func healthCheck(k8sClient k8si.IK8s, namespace string, podName string) (bool, error) {
	ns, err := k8sClient.GetNamespace(namespace)
	if err != nil {
		return false, err
	}
	pods, err := ns.FindPods(podName)
	if err != nil {
		return false, err
	}


	ok := underscore.Chain(pods).All(func(r k8si.IK8sPod, _ int) bool {
		k8sPod := r.GetData()
		if k8sPod.Status.ContainerStatuses == nil {
			return false
		}

		return underscore.Chain(
			k8sPod.Status.ContainerStatuses,
		).All(func(cr v1.ContainerStatus, _ int) bool {
			return cr.Ready
		})
	})

	return ok, nil
}

func getPodLogByPod(k8sClientSet *kubernetes.Clientset, namespace string, podName string, ctx context.Context)(string, error)  {
	opts := &v1.PodLogOptions{
		Follow: false,
	}
	request := k8sClientSet.CoreV1().Pods(namespace).GetLogs(podName, opts)
	readCloser, err := request.Stream(ctx)
	if err != nil {
		return "", err
	}

	defer readCloser.Close()

	reader := bufio.NewReader(readCloser)
	logStr := ""
	logBytes := []byte{}
	for {
		bytes, err := reader.ReadByte()
		logBytes = append(logBytes, bytes)
		if err != nil {
			if err != io.EOF {
				return "", err
			}
			break
		}
	}

	logStr = string(logBytes)

	return logStr, nil
}

func getDeploymentLog(k8sClient k8si.IK8s, namespace string, podName string, ctx context.Context) (string, error) {

	k8sClientSet, err := k8sClient.GetClientSet()
	if err != nil {
		return "", err
	}

	ns, err := k8sClient.GetNamespace(namespace)
	if err != nil {
		return "", err
	}
	myPod, err := ns.FindLatestPods(podName)
	if err != nil {
		return "", err
	}

	if myPod != nil {
		firstPodName := myPod.GetData().Name
		return getPodLogByPod(k8sClientSet, namespace, firstPodName, ctx)
	}
	return "", fmt.Errorf("找不到pod:%s", podName)
}

/**
 获取容器日志接口
*/
func GetDeploymentLog(ctx *gin.Context) {
	var reqData domain.K8sPodBaseRequest
	if err := ctx.ShouldBindJSON(&reqData); err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	k8s, err := getK8sClient(reqData.ConfigFileStr)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	logStr, err := getDeploymentLog(k8s, reqData.Namespace, reqData.PodName, ctx)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}
	ctx.JSON(http.StatusOK, Response{
		Status:  "ok",
		Message: "success",
		Data:  logStr,
	})
}

/**
 心跳检测接口
 */
func HealthCheck(ctx *gin.Context)  {
	var reqData domain.K8sPodBaseRequest
	if err := ctx.ShouldBindJSON(&reqData); err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	k8s, err := getK8sClient(reqData.ConfigFileStr)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	ok, err := healthCheck(k8s, reqData.Namespace, reqData.PodName)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	ctx.JSON(http.StatusOK, Response{
		Status:  "ok",
		Message: "success",
		Data:  ok,
	})
}

/**
 部署接口
 */
func Deploy(ctx *gin.Context) {
	var reqData domain.K8sRequest
	if err := ctx.ShouldBindJSON(&reqData); err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	coder := utils.Coder{}
	deploymentYmlText, err := coder.Decode(reqData.DeploymentYmlText)

	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	k8s, err := getK8sClient(reqData.ConfigFileStr)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	if err := deployImpl(k8s, deploymentYmlText, ctx); err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	ctx.JSON(http.StatusOK, Response{
		Status:  "ok",
		Message: "success",
		Data:  nil,
	})
}

func getK8sClient(configFileStr string) (k8si.IK8s, error)  {
	coder := utils.Coder{}
	configFileStr, err := coder.Decode(configFileStr)
	if err != nil {
		return nil, err
	}

	k8s := k8ssvc.New(configFileStr)
	return k8s, nil
}

func getNamespace(configFileStr string, namespace string) (k8si.IK8sNamespace, error)  {
	k8s, err := getK8sClient(configFileStr)
	if err != nil {
		return nil, err
	}

	ns, err := k8s.GetNamespace(namespace)
	if err != nil {
		return nil, err
	}

	return ns, nil
}

/**
 * 获取deployment列表
 */
func GetDeployments(ctx *gin.Context) {
	var reqData domain.K8sDeployBaseRequest
	if err := ctx.ShouldBindJSON(&reqData); err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	ns, err := getNamespace(reqData.ConfigFileStr, reqData.Namespace)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	deploymentList, err := ns.FindDeployments(reqData.DeploymentName)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	var deploymentDataList []domain.K8sDeploymentData

	underscore.Chain(deploymentList).Map(func(d k8si.IK8sDeployment, _ int) *domain.K8sDeploymentData {
		data := d.GetData()
		return &domain.K8sDeploymentData{
			Name: data.Name,
			Status: data.Status,
			Spec: data.Spec,
		}
	}).Value(&deploymentDataList)

	ctx.JSON(http.StatusOK, Response{
		Status:  "ok",
		Message: "success",
		Data:  deploymentDataList,
	})
}

func GetPods(ctx *gin.Context)  {
	var reqData domain.K8sReplicaSetRequest
	if err := ctx.ShouldBindJSON(&reqData); err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	ns, err := getNamespace(reqData.ConfigFileStr, reqData.Namespace)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	// 查找pod
	rName := reqData.ReplicaSetName

	// 通过rName查找pod
	var k8sPods [] v1.Pod
	pods, err := ns.FindPods(rName)
	if err != nil {
		ctx.JSON(http.StatusOK, Response{
			Status:  "ok",
			Message: "success",
			Data: k8sPods,
		})
		return
	}

	underscore.Chain(pods).Map(func(pod k8si.IK8sPod, _ int) v1.Pod {
		return pod.GetData()
	}).Value(&k8sPods)

	ctx.JSON(http.StatusOK, Response{
		Status:  "ok",
		Message: "success",
		Data:  k8sPods,
	})
}

func GetReplicaSet(ctx *gin.Context) {
	var reqData domain.K8sDeployBaseRequest
	if err := ctx.ShouldBindJSON(&reqData); err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	ns, err := getNamespace(reqData.ConfigFileStr, reqData.Namespace)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	replicaSet, err := ns.FindReplicaSet(reqData.DeploymentName)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	var replicaSetDataList [] typeV1.ReplicaSet
	underscore.Chain(replicaSet).Map(func(d k8si.IK8sReplicaSet, _ int) typeV1.ReplicaSet {
		data := d.GetData()
		return data
	}).Value(&replicaSetDataList)

	ctx.JSON(http.StatusOK, Response{
		Status:  "ok",
		Message: "success",
		Data:  replicaSetDataList,
	})

}

/**
 * 获取容器pod的资源使用情况
 */
func GetPodMetrics(ctx *gin.Context) {
	var reqData domain.K8sPodBaseRequest
	if err := ctx.ShouldBindJSON(&reqData); err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	k8s, err := getK8sClient(reqData.ConfigFileStr)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	k8sMetrics, err := k8s.GetMetrics()
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	podResouces, err := k8sMetrics.GetPodMetricsData(reqData.Namespace)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	ctx.JSON(http.StatusOK, Response{
		Status:  "ok",
		Message: "success",
		Data:  podResouces,
	})
}

/**
获取容器日志接口
*/
func GetPodLog(ctx *gin.Context) {
	var reqData domain.K8sPodBaseRequest
	if err := ctx.ShouldBindJSON(&reqData); err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	k8s, err := getK8sClient(reqData.ConfigFileStr)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	k8sClientSet, err := k8s.GetClientSet()
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	logStr, err := getPodLogByPod(k8sClientSet, reqData.Namespace, reqData.PodName, ctx)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}
	ctx.JSON(http.StatusOK, Response{
		Status:  "ok",
		Message: "success",
		Data:  logStr,
	})
}

/**
 * 获取deployment列表
 */
func RollbackRevision(ctx *gin.Context) {
	var reqData domain.K8sRollbackRequest
	if err := ctx.ShouldBindJSON(&reqData); err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	ns, err := getNamespace(reqData.ConfigFileStr, reqData.Namespace)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	err = ns.Rollback(reqData.DeploymentName, reqData.RollbackReplicaSetName)
	if err != nil {
		HandleError(http.StatusBadRequest, ctx, err)
		return
	}

	ctx.JSON(http.StatusOK, Response{
		Status:  "ok",
		Message: "success",
		Data:  nil,
	})
}

/**
 * 心跳接口
 */
func Info(ctx *gin.Context) {
	ctx.JSON(http.StatusOK, Response{
		Status:  "ok",
		Message: "success",
		Data:  nil,
	})
}